﻿/*=====================================================================
 * Arquivo:	PropertyList.cs
 * 
 * Sumário: Definição de uma classe auxiliar que implementa uma coleção
 *					de propriedades armazenadas no formato Name=Value.
 * 
 * Autor:		Siddhartha Argollo
 * 
 * Copyright (C) TecnoTRENDS.  Todos os direitos reservados.
 =====================================================================*/
using System;
using System.Xml;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.Xml.Serialization;
using System.Runtime.Serialization;
using System.Reflection;

namespace DaCredito
{
    /// <summary>
    /// Classe auxiliar que implementa uma coleção de propriedades armazenadas
    /// no formato Name=Value.
    /// </summary>
    [Serializable]
    public class PropertyList : IDictionary, ICollection, IEnumerable, ISerializable
    {
        /// <summary>
        /// Texto a ser impresso quando a lista estiver vazia.
        /// </summary>
        public const string EMPTY_LIST = "<empty>";

        /// <summary>
        /// O separador padrão a ser usado para separar os pares da lista.
        /// </summary>
        public const string DEFAULT_SEPARATOR = ";";

        /// <summary>
        /// Texto a ser impresso quando o valor da propriedade for nulo.
        /// </summary>
        public const string NULL_VALUE = "null";

        // Variável de controle para BeginUpdate/EndUpdate
        private int iiUpdateCount = 0;
        // Armazena os pares da lista
        private Hashtable ioItems;
        // Armazena os tipos derivados as AsProperty disponíveis no objeto
        private Hashtable ioPropTypes;
        // Armazena os valores padrões das propriedades
        private PropertyListDefaults ioDefaults;
        // Aramzena o nome das propriedades que não devem ser consideradas na
        // serialização do objeto.
        private List<string> ioNonSerialized;
        //private PropertyListMetaDataCollection ioMetaData;

        /// <summary>
        /// Função usada pelo método <see cref="AddRange(IDictionary,CanAddValue)"/>
        /// para indicar se o par formado por <c>asKey</c> 
        /// e <c>aoValue</c> pode ser adicionado à lista.
        /// </summary>
        public delegate bool CanAddValue(string asKey, object aoValue);

        /// <summary>
        /// Evento disparado quando a lista de propriedades é alterada, seja durante a adição
        /// de elementos ou na sua remoção.
        /// </summary>
        public event EventHandler<PropertyListChangedEventArgs> Changed;

        /// <summary>
        /// Cria uma lista de propriedades vazia.
        /// </summary>
        public PropertyList()
            : this((string[])null)
        {
        }

        /// <summary>
        /// Cria uma lista de propriedades, copiando os valores contidos no objeto para
        /// o objeto PropertyList passado como parâmetro, levando em conta as informações
        /// sobre serialização e de valores padrões.
        /// </summary>
        public PropertyList(PropertyList aoSource)
            : this((string[])null)
        {
            aoSource.CopyTo(this);
        }

        /// <summary>
        /// Cria uma lista de propriedades, contendo os pares passados em <c>asKeyValues</c>.
        /// </summary>
        /// <param name="asKeyValues">Array de propriedades a serem armazenadas na lista, onde cada
        /// entrada deve estar no formato key=value.</param>
        public PropertyList(string[] asKeyValues)
        {
            this.ioItems = new Hashtable();
            this.ioPropTypes = new Hashtable();
            this.ioDefaults = new PropertyListDefaults(this);
           // this.ioMetaData = new PropertyListMetaDataCollection(this);
            this.ioNonSerialized = new List<string>();
            if (asKeyValues != null)
                this.AddRange(asKeyValues);
        }

        /// <summary>
        /// Construtor usado para a desserialização do objeto.
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        protected PropertyList(SerializationInfo info, StreamingContext context)
        {
            this.ioPropTypes = new Hashtable();
            this.ioItems = (Hashtable)info.GetValue("Items", typeof(Hashtable));
            this.ioNonSerialized = (List<string>)info.GetValue("NonSerialized", typeof(List<string>));
            this.ioDefaults = new PropertyListDefaults(this);
            this.ioDefaults.Values = (Hashtable)info.GetValue("Defaults", typeof(Hashtable));
          //  this.ioMetaData = new PropertyListMetaDataCollection(this);
          //  this.ioMetaData.MetaData = (Dictionary<string, PropertyMetaData>)
             // info.GetValue("MetaData", typeof(Dictionary<string, PropertyMetaData>));
        }

        #region Public Properties

        /// <summary>
        /// Retorna o valor de uma determinada propriedade.
        /// </summary>
        public object this[string asKey]
        {
            get { return this.GetValue(asKey); }
            set { this.Dictionary[asKey] = value; }
        }

        /// <summary>
        /// Retorna os valores padrões das propriedades.
        /// </summary>
        public PropertyListDefaults Defaults
        {
            get { return this.ioDefaults; }
        }

      /*  public PropertyListMetaDataCollection MetaData
        {
            get { return this.ioMetaData; }
        }*/

        /// <summary>
        /// Indica se o valor da propriedade difere do seu valor padrão.
        /// </summary>
        /// <param name="lsKey"></param>
        /// <returns>True se o valor atual difere do valor padrão da propriedade,
        /// false caso contrário.</returns>
        public bool IsModified(string lsKey)
        {
            object loValue = this[lsKey];
            object loDefault = Defaults[lsKey];
            // Se existe um valor default, mas não há um valor definido, retorna false
            if ((loValue == null && loDefault != null) ||
                (loValue == null && loDefault == null))
                return false;
            // Se não existe um valor default, retorna true
            if (loValue != null && loDefault == null)
                return true;
            // Se o valor definido for diferente do default, retorna true
            return !loValue.Equals(loDefault);
        }

        /// <summary>
        /// Retorna uma interface ICollection contendo os nomes das propriedades
        /// armazenadas na lista.
        /// </summary>
        public ICollection<string> Keys
        {
            get
            {
                List<string> loList = new List<string>();
                foreach (DictionaryEntry loEntry in this.ioItems)
                    loList.Add(loEntry.Key.ToString());
                return loList;
            }
        }

        /// <summary>
        /// Retorna uma interface ICollection contendo os valores das propriedades
        /// armazenadas na lista.
        /// </summary>
        public ICollection<object> Values
        {
            get
            {
                List<object> loValues = new List<object>();
                foreach (DictionaryEntry loEntry in this.Dictionary)
                    loValues.Add(this.GetValue(loEntry.Key.ToString()));
                return loValues;
            }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como booleano, se
        /// possível.
        /// </summary>
        public AsBoolProperty AsBool
        {
            get { return (AsBoolProperty)GetAsProperty(typeof(AsBoolProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como string, se
        /// possível.
        /// </summary>
        public AsStringProperty AsString
        {
            get { return (AsStringProperty)GetAsProperty(typeof(AsStringProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como int, se
        /// possível.
        /// </summary>
        public AsIntProperty AsInt
        {
            get { return (AsIntProperty)GetAsProperty(typeof(AsIntProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como long, se
        /// possível.
        /// </summary>
        public AsLongProperty AsLong
        {
            get { return (AsLongProperty)GetAsProperty(typeof(AsLongProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como float, se
        /// possível.
        /// </summary>
        public AsFloatProperty AsFloat
        {
            get { return (AsFloatProperty)GetAsProperty(typeof(AsFloatProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como double, se
        /// possível.
        /// </summary>
        public AsDoubleProperty AsDouble
        {
            get { return (AsDoubleProperty)GetAsProperty(typeof(AsDoubleProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como decimal, se
        /// possível.
        /// </summary>
        public AsDecimalProperty AsDecimal
        {
            get { return (AsDecimalProperty)GetAsProperty(typeof(AsDecimalProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como DateTime, se
        /// possível.
        /// </summary>
        public AsDateTimeProperty AsDateTime
        {
            get { return (AsDateTimeProperty)GetAsProperty(typeof(AsDateTimeProperty)); }
        }

        /// <summary>
        /// Retorna o valor de uma determinada propriedade como PropertyList, se
        /// possível.
        /// </summary>
        public AsPropertyListProperty AsPropertyList
        {
            get { return (AsPropertyListProperty)GetAsProperty(typeof(AsPropertyListProperty)); }
        }

        #endregion

        #region Public Methods

        public Type GetDataType(string asKey)
        {
            object loValue = this.GetValue(asKey);
            if (loValue != null)
                return loValue.GetType();
            return null;
        }

        public bool IsNumber(string asKey)
        {
            if (!this.IsEmpty(asKey))
                return ObjectUtils.IsNumber(this[asKey]);
            return false;
        }

        public bool IsText(string asKey)
        {
            if (!this.IsEmpty(asKey))
                return ObjectUtils.IsText(this[asKey]);
            return false;
        }

        public bool IsOrdinal(string asKey)
        {
            if (!this.IsEmpty(asKey))
                return ObjectUtils.IsOrdinal(this[asKey]);
            return false;
        }

        public bool IsReal(string asKey)
        {
            if (!this.IsEmpty(asKey))
                return ObjectUtils.IsBoolean(this[asKey]);
            return false;
        }

        public bool IsBoolean(string asKey)
        {
            if (!this.IsEmpty(asKey))
                return ObjectUtils.IsBoolean(this[asKey]);
            return false;
        }

        /// <summary>
        /// Retorna um objeto PropertyList gerado a partir de uma seção XML do
        /// arquivo de configuração da aplicação.
        /// </summary>
        /// <param name="asName">Nome da seção do App.Config onde está definido
        /// o XML das propriedades.</param>
        /// <returns>
        /// Um objeto PropertyList gerado a partir de uma seção XML do
        /// arquivo de configuração da aplicação.
        /// </returns>
        public static PropertyList GetSection(string asName)
        {
            return (PropertyList)ConfigurationManager.GetSection(asName);
        }

        /// <summary>
        /// Retorna uma lista das chaves armazenadas no objeto em ordem alfabética.
        /// </summary>
        /// <returns>
        /// Lista das chaves armazenadas no objeto em ordenadas alfabeticamente.
        /// </returns>
        public string[] GetSortedKeys()
        {
            return this.GetSortedKeys(null);
        }

        /// <summary>
        /// Retorna uma lista das chaves armazenadas no objeto, ordenada pelo objeto 
        /// Comparer.
        /// </summary>
        /// <param name="aoComparer">Objeto usado para a ordenação das chaves.</param>
        /// <returns>
        /// Lista das chaves armazenadas no objeto em ordenadas pelo objeto Comparer.
        /// </returns>
        public string[] GetSortedKeys(Comparer aoComparer)
        {
            string[] lsKeys = new string[this.Count];
            this.Keys.CopyTo(lsKeys, 0);
            Array.Sort(lsKeys, aoComparer);
            return lsKeys;
        }

        /// <summary>
        /// Define o início de várias atualizações na coleção.
        /// </summary>
        public void BeginUpdate()
        {
            this.iiUpdateCount++;
        }

        /// <summary>
        /// Define o fim de várias atualizações na coleção.
        /// </summary>
        public void EndUpdate()
        {
            if (this.iiUpdateCount > 0)
            {
                this.iiUpdateCount--;
                if (this.iiUpdateCount == 0)
                    this.OnChanged(PropertyListChangeReason.EndUpdate, "");
            }
        }

        /// <summary>
        /// Retorna a lista de propriedades não serializáveis.
        /// </summary>
        public List<string> NonSerialized
        {
            get { return this.ioNonSerialized; }
        }

        /// <summary>
        /// Verifica se existe um valor armazenado para a propriedade.
        /// </summary>
        /// <param name="asKey">Nome da propriedade.</param>
        /// <returns>
        /// True se não existe um valor armazenado para a propriedade.
        /// </returns>
        public bool IsNull(string asKey)
        {
            return this.GetValue(asKey) == null;
        }

        /// <summary>
        /// Verifica se existe um valor armazenado para a propriedade e, no caso
        /// de seu tipo ser uma string, se o valor é uma string vazia ("").
        /// </summary>
        /// <param name="asKey">Nome da propriedade.</param>
        /// <returns>
        /// True se não existe um valor armazenado para a propriedade ou se
        /// o valor é uma string vazia.
        /// </returns>
        public bool IsEmpty(string asKey)
        {
            return (this.GetValue(asKey) == null) || (this.GetValue(asKey).ToString().Trim().Length == 0);
        }

        /// <summary>
        /// Armazena os valores de um PropertyList na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        public void AddRange(PropertyList aoList)
        {
            this.AddRange(aoList, false);
            foreach (DictionaryEntry loEntry in aoList.Defaults.Values)
                if (!this.Defaults.Values.ContainsKey(loEntry.Key))
                    this.Defaults.Values.Add(loEntry.Key, loEntry.Value);
            this.NonSerialized.AddRange(aoList.NonSerialized);
           // this.ioMetaData.AddRange(aoList.MetaData);
        }

        /// <summary>
        /// Copia os valores contidos no objeto para o objeto PropertyList passado como 
        /// parâmetro, levando em conta as informações sobre serialização e de valores
        /// padrões.
        /// </summary>
        /// <param name="aoList">Objeto PropertyList de destino.</param>
        public void CopyTo(PropertyList aoList)
        {
            aoList.Clear();
            aoList.AddRange(this);
        }

        /// <summary>
        /// Armazena os valores de um IDictionary na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        public void AddRange(IDictionary aoList)
        {
            this.AddRange(aoList, false, null);
        }

        /// <summary>
        /// Armazena os valores de um IDictionary na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        /// <param name="abUpdateOnly">Se true, apenas as propriedades de
        /// mesmo nome terão seus valores modificados.</param>
        public void AddRange(IDictionary aoList, bool abUpdateOnly)
        {
            this.AddRange(aoList, abUpdateOnly, null);
        }

        /// <summary>
        /// Armazena os valores de um IDictionary na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        /// <param name="aoCanAdd">Objeto usado para filtrar as propriedades que devem
        /// ser adicionadas.</param>
        public void AddRange(IDictionary aoList, CanAddValue aoCanAdd)
        {
            this.AddRange(aoList, false, aoCanAdd);
        }

        /// <summary>
        /// Armazena os valores de um IDictionary na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        /// <param name="abUpdateOnly">Se true, apenas as propriedades de
        /// mesmo nome terão seus valores modificados.</param>
        /// <param name="aoCanAdd">Objeto usado para filtrar as propriedades que devem
        /// ser adicionadas.</param>
        public void AddRange(IDictionary aoList, bool abUpdateOnly, CanAddValue aoCanAdd)
        {
            this.BeginUpdate();
            try
            {
                foreach (DictionaryEntry loEntry in aoList)
                {
                    if (abUpdateOnly)
                    {
                        if (this.Contains(loEntry.Key.ToString()))
                            if (aoCanAdd == null || aoCanAdd(loEntry.Key.ToString(), loEntry.Value))
                                this[loEntry.Key.ToString()] = loEntry.Value;
                    }
                    else
                        if (aoCanAdd == null || aoCanAdd(loEntry.Key.ToString(), loEntry.Value))
                            this.Add(loEntry.Key.ToString(), loEntry.Value);
                }
            }
            finally
            {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// Armazena os valores de um IDictionary&lt;K, V&gt; na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        public void AddRange<K, V>(IDictionary<K, V> aoList)
        {
            this.AddRange(aoList, false, null);
        }

        /// <summary>
        /// Armazena os valores de um IDictionary&lt;K, V&gt;; na lista de propriedades.
        /// </summary>
        /// <param name="aoList">Fonte de valores.</param>
        /// <param name="abUpdateOnly">Se true, apenas as propriedades de
        /// mesmo nome terão seus valores modificados.</param>
        /// <param name="aoCanAdd">Objeto usado para filtrar as propriedades que devem
        /// ser adicionadas.</param>
        public void AddRange<K, V>(IDictionary<K, V> aoList, bool abUpdateOnly, CanAddValue aoCanAdd)
        {
            this.BeginUpdate();
            try
            {
                foreach (KeyValuePair<K, V> loPair in aoList)
                {
                    if (abUpdateOnly)
                    {
                        if (this.Contains(loPair.Key.ToString()))
                            if (aoCanAdd == null || aoCanAdd(loPair.Key.ToString(), loPair.Value))
                                this[loPair.Key.ToString()] = loPair.Value;
                    }
                    else
                        if (aoCanAdd == null || aoCanAdd(loPair.Key.ToString(), loPair.Value))
                            this.Add(loPair.Key.ToString(), loPair.Value);
                }
            }
            finally
            {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// Armazena os valores de um IList na lista de propriedades.
        /// </summary>
        /// <remarks>
        /// Cada valor da lista deve estar no formato key=value ou conter apenas
        /// o nome da propriedade.
        /// </remarks>
        /// <param name="aoKeyValues">Fonte de valores.</param>
        public void AddRange(IList aoKeyValues)
        {
            this.AddRange(aoKeyValues, null);
        }

        /// <summary>
        /// Armazena os valores de um IList na lista de propriedades.
        /// </summary>
        /// <remarks>
        /// Cada valor da lista deve estar no formato key=value ou conter apenas
        /// o nome da propriedade.
        /// </remarks>
        /// <param name="aoKeyValues">Fonte de valores.</param>
        /// <param name="aoCanAdd">Objeto usado para filtrar as propriedades que devem
        /// ser adicionadas.</param>
        public void AddRange(IList aoKeyValues, CanAddValue aoCanAdd)
        {
            this.BeginUpdate();
            try
            {
                foreach (string lsEntry in aoKeyValues)
                {
                    if (aoCanAdd != null)
                    {
                        DictionaryEntry loEntry = this.GetEntry(lsEntry);
                        if (aoCanAdd(loEntry.Key.ToString(), loEntry.Value))
                            this.Add(loEntry.Key.ToString(), loEntry.Value);
                    }
                    else
                        this.Add(lsEntry);
                }
            }
            finally
            {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// Sobrecarga do método Equals. Duas coleções serão iguais se todos os
        /// seus membros forem iguais.
        /// </summary>
        /// <param name="obj">
        /// Objeto a ser comparado. Deve ser do tipo PropertyList.
        /// </param>
        /// <returns>True se todos os membros das duas coleções forem iguais.</returns>
        public override bool Equals(object obj)
        {
            if (this == obj)
                return true;
            if (obj == null || !(obj is PropertyList))
                return false;
            PropertyList loProps = (PropertyList)obj;
            if (this.ioItems.Count != loProps.Count)
                return false;
            foreach (DictionaryEntry entry in this.ioItems)
            {
                if (!loProps.Contains((string)entry.Key))
                    return false;
                object loThisValue = this.ioItems[entry.Key];
                object loThatValue = loProps.ioItems[entry.Key];
                if (loThisValue != null && loThatValue != null)
                {
                    if (!loThisValue.Equals(loThatValue))
                        return false;
                }
                else if (loThisValue != loThatValue)
                    return false;
            }
            return true;
        }

        /// <summary>
        /// Retorna o HashCode do objeto.
        /// </summary>
        /// <returns>O HashCode do objeto.</returns>
        public override int GetHashCode()
        {
            return this.ioItems.GetHashCode();
        }

        /// <summary>
        /// Retorna a lista de propriedades como uma string, no formato
        /// Name=Value e separados por <c>DEFAULT_SEPARATOR</c>.
        /// </summary>
        /// <returns>A lista de propriedades em uma string.</returns>
        public override string ToString()
        {
            return this.ToString(DEFAULT_SEPARATOR);
        }

        /// <summary>
        /// Retorna a lista de propriedades como uma string, no formato
        /// Name=Value e separados pela string "asSeparator".
        /// </summary>
        /// <param name="asSeparator">Separador das propriedades.</param>
        /// <returns>A lista de propriedades em uma string.</returns>
        public string ToString(string asSeparator)
        {
            string lsList = CollectionUtils.ToString(this, asSeparator, "");
            if (lsList == "")
                return EMPTY_LIST;
            return lsList;
        }

        /// <summary>
        /// Armazena na coleção as propriedades contidas na string "asValue".
        /// A string deve conter propriedades no formato Name=Value, separadas
        /// pelo separador ";".
        /// </summary>
        /// <param name="asValue">Lista de propriedades no formato string.</param>
        public void SetString(string asValue)
        {
            this.SetString(asValue, ";");
        }

        /// <summary>
        /// Armazena na coleção as propriedades contidas na string "asValue".
        /// A string deve conter propriedades no formato Name=Value, separadas
        /// pelo separador "asSeparator".
        /// </summary>
        /// <param name="asValue">Lista de propriedades no formato string.</param>
        /// <param name="asSeparator">Separador das propriedades.</param>
        public void SetString(string asValue, string asSeparator)
        {
            this.BeginUpdate();
            try
            {
                int liIndex = asValue.IndexOf(asSeparator);
                while (liIndex >= 0)
                {
                    string lsValue = asValue.Substring(0, liIndex);
                    if (lsValue.Length > 0)
                        this.Add(lsValue);
                    asValue = asValue.Substring(liIndex + asSeparator.Length);
                    liIndex = asValue.IndexOf(asSeparator);
                }
                if (asValue.Length > 0)
                    this.Add(asValue);
            }
            finally
            {
                this.EndUpdate();
            }
        }

        /// <summary>
        /// Adiciona uma propriedade à coleção. A propriedade pode estar no
        /// formato Name=Value ou apenas Name. Caso o valor seja omitido, 
        /// será adicionado como uma string vazia.
        /// </summary>
        /// <param name="asText">Propriedade a ser adicionada.</param>
        public void Add(string asText)
        {
            DictionaryEntry loEntry = GetEntry(asText);
            this.Add(loEntry.Key.ToString(), loEntry.Value);
        }

        /// <summary>
        /// Adiciona uma propriedade à coleção.
        /// </summary>
        /// <param name="asKey">Nome da propriedade.</param>
        /// <param name="asValue">Valor da propriedade.</param>
        public void Add(string asKey, object asValue)
        {
            if (this.Dictionary.Contains(asKey))
                this[asKey] = asValue;
            else
                this.Dictionary.Add(asKey, asValue);
            this.OnChanged(PropertyListChangeReason.ItemAdded, asKey);
        }

        /// <summary>
        /// Adiciona uma lista de propriedades à coleção.
        /// </summary>
        /// <param name="aoList">Lista de propriedades</param>
        public void Add(IDictionary aoList)
        {
            foreach (DictionaryEntry entry in aoList)
                this.Add(entry.Key.ToString(), entry.Value);
        }

        /// <summary>
        /// Indica se a lista contém a chave requisitada.
        /// </summary>
        /// <param name="asKey">Nome da chave a ser procurada.</param>
        /// <returns>
        /// True se a chave estiver armazenada na lista, false caso contrário.
        /// </returns>
        public bool Contains(string asKey)
        {
            return this.Dictionary.Contains(asKey);
        }

        /// <summary>
        /// Indica se a lista contém o valor requisitado.
        /// </summary>
        /// <param name="aoValue">Valor a a ser procurado.</param>
        /// <returns>
        /// True se o valor for encontrado, false caso contrário.
        /// </returns>
        public bool ContainsValue(object aoValue)
        {
            foreach (DictionaryEntry loEntry in this.Dictionary)
                if (loEntry.Value != null)
                {
                    if (loEntry.Value.Equals(this.GetValue(loEntry.Key.ToString())))
                        return true;
                }
                else
                {
                    if (loEntry.Value == this.GetValue(loEntry.Key.ToString()))
                        return true;
                }
            return false;
        }

        /// <summary>
        /// Retorna a quantidade de elementos contidos na lista.
        /// </summary>
        public int Count
        {
            get { return this.Dictionary.Count; }
        }

        /// <summary>
        /// Copia os valores contidos no objeto para o array passado como parâmetro,
        /// a partir do índice requisitado.
        /// </summary>
        /// <remarks>
        /// Os pares da lista de propriedades são armazenados no array encapsulados em
        /// um objeto DictionaryEntry.
        /// </remarks>
        /// <param name="aoArray">Array para onde os valores serão copiados; deve ser capaz
        /// de suportar objetos do tipo DictionaryEntry.</param>
        /// <param name="aiIndex">Índice que deve ser usado como início da cópia.</param>
        public void CopyTo(Array aoArray, int aiIndex)
        {
            if (aoArray == null)
                throw new ArgumentNullException("aoArray");
            if (aoArray.Rank > 1)
                throw new ArgumentException("The array cannot be multidimensional.", "aoArray");
            if (aiIndex < 0)
                throw new ArgumentOutOfRangeException("aiIndex", aiIndex, "Index out of range.");
            if (aoArray.Length - aiIndex < this.Dictionary.Count)
                throw new ArgumentException("The number of elements in the source is greater " +
                  "than the available space from index to the end of the destination array.", "aoArray");
            int liCount = aiIndex;
            foreach (DictionaryEntry loEntry in this.Dictionary)
                aoArray.SetValue(new DictionaryEntry(loEntry.Key, this.GetValue(loEntry.Key.ToString())), liCount++);
        }

        /// <summary>
        /// Copia os valores contidos no objeto para o objeto IDictionary passado como 
        /// parâmetro.
        /// </summary>
        /// <param name="aoList">Objeto IDictionary de destino.</param>
        public void CopyTo(IDictionary aoList)
        {
            foreach (DictionaryEntry loEntry in this.Dictionary)
                aoList.Add(loEntry.Key, this.GetValue(loEntry.Key.ToString()));
        }

        /// <summary>
        /// Copia os valores contidos no objeto para o objeto IDictionary&lt;K, V&gt; 
        /// passado como parâmetro.
        /// </summary>
        /// <param name="aoList">Objeto IDictionary de destino.</param>
        public void CopyTo<K, V>(IDictionary<K, V> aoList)
        {
            foreach (DictionaryEntry loEntry in this.Dictionary)
                aoList.Add((K)loEntry.Key, (V)this.GetValue(loEntry.Key.ToString()));
        }

        /// <summary>
        /// Retorna o objeto IDictionaryEnumerator.
        /// </summary>
        /// <returns></returns>
        public IDictionaryEnumerator GetEnumerator()
        {
            return new PropertyListEnumerator(this);
        }

        /// <summary>
        /// Apaga todos os valores da lista.
        /// </summary>
        public void Clear()
        {
            this.Dictionary.Clear();
            this.Defaults.Clear();
            this.NonSerialized.Clear();
            this.OnChanged(PropertyListChangeReason.Clear, "");
        }

        /// <summary>
        /// Remove a propriedade requisitada da lista.
        /// </summary>
        /// <param name="asKey">Propriedade a ser removida.</param>
        public void Remove(string asKey)
        {
            this.Dictionary.Remove(asKey);
            this.Defaults.Remove(asKey);
            this.NonSerialized.Remove(asKey);
            this.OnChanged(PropertyListChangeReason.ItemRemoved, asKey);
        }

        #endregion

        #region ISerializable Members

        /// <summary>
        /// Método usado na serialização do objeto.
        /// </summary>
        /// <param name="info">Objeto SerializationInfo.</param>
        /// <param name="context">Objeto StreamingContext.</param>
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Items", this.Dictionary);
            info.AddValue("Defaults", this.Defaults.Values);
            info.AddValue("NonSerialized", this.NonSerialized);
           // info.AddValue("MetaData", this.ioMetaData.MetaData);
        }

        #endregion

        #region Protected Members

        /// <summary>
        /// Retorna o objeto interno que armazena as propriedades e seus valores.
        /// </summary>
        protected IDictionary Dictionary
        {
            get { return this.ioItems; }
        }

        /// <summary>
        /// Cria um objeto DictionaryEntry contendo o par contido na string <c>asText</c>.
        /// A string deve estar no formato "key=value", ou somente "key" no caso de um valor nulo.
        /// </summary>
        /// <param name="asText"></param>
        /// <returns></returns>
        protected DictionaryEntry GetEntry(string asText)
        {
            string lsKey;
            string lsValue = null;
            int liIndex = asText.IndexOf("=");
            if (liIndex > 0)
            {
                lsKey = asText.Substring(0, liIndex);
                lsValue = asText.Substring(liIndex + 1);
                if (lsValue == NULL_VALUE)
                    lsValue = null;
            }
            else
                lsKey = asText;
            return new DictionaryEntry(lsKey, lsValue);
        }

        /// <summary>
        /// Retorna o valor armazenado para a chave <c>asKey</c>.
        /// </summary>
        /// <remarks>
        /// Este método irá retornar o valor atual da propriedade requisitada,
        /// mas, se esse valor for nulo e a propriedade possuir um valor padrão,
        /// este último será retornado.
        /// </remarks>
        /// <param name="asKey">Nome da propriedade.</param>
        /// <returns>O valor atual da propriedade.</returns>
        protected object GetValue(string asKey)
        {
            object loResult = null;
            if (this.Dictionary.Contains(asKey))
            {
                loResult = this.Dictionary[asKey];
                if (loResult == null && this.Defaults.ContainsKey(asKey))
                    loResult = this.Defaults[asKey];
            }
            return loResult;
        }

        /// <summary>
        /// Retorna o objeto AsProperty a partir do tipo requisitado.
        /// </summary>
        /// <param name="atProperty">Tipo do objeto AsProperty a ser instanciado.</param>
        /// <returns>O objeto AsProperty.</returns>
        protected AsProperty GetAsProperty(Type atProperty)
        {
            if (!this.ioPropTypes.ContainsKey(atProperty))
                // Cria as propriedades indexadas on-the-fly
                this.ioPropTypes.Add(atProperty, Activator.CreateInstance(atProperty, new object[] { this }));
            return (AsProperty)this.ioPropTypes[atProperty];
        }

        /// <summary>
        /// Chamado quando a lista é modificada.
        /// </summary>
        protected virtual void OnChanged(PropertyListChangeReason aoChangeReason, string asKey)
        {
            if (this.Changed != null && this.iiUpdateCount == 0)
            {
                PropertyListChangedEventArgs loArgs = new PropertyListChangedEventArgs(aoChangeReason, asKey);
                this.Changed(this, loArgs);
            }
        }

        #endregion

        #region IDictionary Explicit Members

        object IDictionary.this[object key]
        {
            get { return this[key.ToString()]; }
            set { this[key.ToString()] = value; }
        }

        ICollection IDictionary.Keys
        {
            get { return this.ioItems.Keys; }
        }

        ICollection IDictionary.Values
        {
            get
            {
                ArrayList loList = new ArrayList();
                ICollection<object> loValueList = this.Values;
                foreach (object loValue in loValueList)
                    loList.Add(loValue);
                return loList;
            }
        }

        bool IDictionary.IsReadOnly
        {
            get { return this.Dictionary.IsReadOnly; }
        }

        bool IDictionary.IsFixedSize
        {
            get { return this.Dictionary.IsFixedSize; }
        }

        void IDictionary.Remove(object key)
        {
            this.Remove(key.ToString());
        }

        bool IDictionary.Contains(object key)
        {
            return this.Contains(key.ToString());
        }

        void IDictionary.Add(object key, object value)
        {
            this.Add(key.ToString(), value);
        }

        #endregion

        #region ICollection Explicit Members

        bool System.Collections.ICollection.IsSynchronized
        {
            get { return this.Dictionary.IsSynchronized; }
        }

        object System.Collections.ICollection.SyncRoot
        {
            get { return this.Dictionary.SyncRoot; }
        }

        #endregion

        #region IEnumerable Explicit Members

        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        #region PropertyListEnumerator

        /// <summary>
        /// Implementa um objeto IDictionaryEnumerator para a classe PropertyList.
        /// </summary>
        protected class PropertyListEnumerator : IDictionaryEnumerator
        {
            private int iiCursor = -1;
            private PropertyList ioOwner;
            private DictionaryEntry[] ioEntries;

            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto PropertyList.</param>
            public PropertyListEnumerator(PropertyList aoOwner)
            {
                this.ioOwner = aoOwner;
                this.ioEntries = new DictionaryEntry[aoOwner.Dictionary.Count];
                this.ioOwner.CopyTo(this.ioEntries, 0);
            }

            #region IDictionaryEnumerator Members

            /// <summary></summary>
            public object Key
            {
                get { return this.Entry.Key; }
            }

            /// <summary></summary>
            public object Value
            {
                get { return this.Entry.Value; }
            }

            /// <summary></summary>
            public DictionaryEntry Entry
            {
                get { return this.ioEntries[this.iiCursor]; }
            }

            #endregion

            #region IEnumerator Members

            /// <summary></summary>
            public void Reset()
            {
                this.iiCursor = -1;
            }

            /// <summary></summary>
            public object Current
            {
                get { return this.Entry; }
            }

            /// <summary></summary><returns></returns>
            public bool MoveNext()
            {
                return (++this.iiCursor < this.ioEntries.Length);
            }

            #endregion

        } // PropertyListEnumerator

        #endregion

        #region PropertyListDefaults

        /// <summary>
        /// Classe auxiliar, que armazena os valores padrões das propriedades de um 
        /// objeto PropertyList.
        /// </summary>
        public class PropertyListDefaults
        {
            private PropertyList ioOwner;
            private Hashtable ioDefaults = new Hashtable();

            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto PropertyList.</param>
            public PropertyListDefaults(PropertyList aoOwner)
            {
                this.ioOwner = aoOwner;
            }

            /// <summary>
            /// Retorna ou altera o valor padrão da propriedade requisitada.
            /// </summary>
            public object this[object aoKey]
            {
                get { return this.ioDefaults[aoKey]; }
                set
                {
                    if (this.ioDefaults.ContainsKey(aoKey))
                        this.ioDefaults[aoKey] = value;
                    else
                        this.ioDefaults.Add(aoKey, value);
                    if (!this.ioOwner.Contains(aoKey.ToString()))
                        this.ioOwner.Add(aoKey.ToString());
                }
            }

            /// <summary>
            /// Indica se a propriedade possui um valor padrão.
            /// </summary>
            /// <param name="asKey">Nome da propriedade.</param>
            /// <returns>
            /// True se a propriedade possuir um valor padrão, false caso contrário.
            /// </returns>
            protected internal bool ContainsKey(object asKey)
            {
                return this.ioDefaults.ContainsKey(asKey);
            }

            /// <summary>
            /// Apaga os valores da lista.
            /// </summary>
            protected internal void Clear()
            {
                this.ioDefaults.Clear();
            }

            /// <summary>
            /// Remove a propriedade da lista de valores padrões.
            /// </summary>
            /// <param name="asKey">Nome da propriedade.</param>
            protected internal void Remove(string asKey)
            {
                this.ioDefaults.Remove(asKey);
            }

            /// <summary>
            /// Retorna ou altera o objeto que armazena os valores padrões.
            /// </summary>
            protected internal Hashtable Values
            {
                get { return this.ioDefaults; }
                set { this.ioDefaults = value; }
            }
        }

        #endregion

        #region AsProperty Classes

        /// <summary>
        /// Classe base para os objetos usados pela classe PropertyList que alteram
        /// o tipo dos valores armazenados na lista.
        /// </summary>
        public class AsProperty
        {
            /// <summary>
            /// Dicionário ao qual o objeto pertence.
            /// </summary>
            protected IDictionary ioOwner;

            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsProperty(IDictionary aoOwner)
            {
                this.ioOwner = aoOwner;
            }
        }

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// boleano.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para boleano, uma exceção será lançada.
        /// </remarks>
        public class AsBoolProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsBoolProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como boleano.
            /// </summary>
            public bool this[string asKey]
            {
                get
                {
                    object loValue = this.ioOwner[asKey];
                    if (loValue == null)
                        return false;
                    else
                        return Convert.ToBoolean(loValue);
                }
                set { this.ioOwner[asKey] = value; }
            }
        } // AsBoolProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// String.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para String, uma exceção será lançada.
        /// </remarks>
        public class AsStringProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsStringProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como String.
            /// </summary>
            /// <remarks>
            /// Caso o valor não possa ser convertido para String, uma exceção será lançada.
            /// </remarks>
            public string this[string asKey]
            {
                get
                {
                    object loValue = this.ioOwner[asKey];
                    if (loValue == null)
                        return "";
                    else
                        return Convert.ToString(loValue);
                }
                set { this.ioOwner[asKey] = value; }
            }
        } // AsStringProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// ordinal.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para ordinal, uma exceção será lançada.
        /// </remarks>
        public class AsOrdinalProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsOrdinalProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como ordinal.
            /// </summary>
            /// <remarks>
            /// Caso o valor não possa ser convertido para ordinal, uma exceção será lançada.
            /// </remarks>
            protected Int64 GetValue(string asKey)
            {
                object loValue = this.ioOwner[asKey];
                if (loValue == null)
                    return 0;
                else
                    return Convert.ToInt64(loValue);
            }
        } // AsOrdinalProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// inteiro.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para inteiro, uma exceção será lançada.
        /// </remarks>
        public class AsIntProperty : AsOrdinalProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsIntProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como inteiro.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para inteiro, uma exceção será lançada.
            /// </remarks>
            public int this[string asKey]
            {
                get { return Convert.ToInt32(GetValue(asKey)); }
                set { this.ioOwner[asKey] = value; }
            }
        } // AsIntProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// long.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para long, uma exceção será lançada.
        /// </remarks>
        public class AsLongProperty : AsOrdinalProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsLongProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como long.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para long, uma exceção será lançada.
            /// </remarks>
            public long this[string asKey]
            {
                get { return GetValue(asKey); }
                set { this.ioOwner[asKey] = value; }
            }
        } // AsLongProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// real.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para real, uma exceção será lançada.
        /// </remarks>
        public class AsRealProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsRealProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como real.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para real, uma exceção será lançada.
            /// </remarks>
            protected double GetValue(string asKey)
            {
                object loValue = this.ioOwner[asKey];
                if (loValue == null)
                    return 0;
                else
                    return Convert.ToDouble(loValue);
            }
        } // AsRealProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// float.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para float, uma exceção será lançada.
        /// </remarks>
        public class AsFloatProperty : AsRealProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsFloatProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como float.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para float, uma exceção será lançada.
            /// </remarks>
            public float this[string asKey]
            {
                get { return (float)GetValue(asKey); }
                set { this.ioOwner[asKey] = value; }
            }
        } // AsFloatProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// double.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para double, uma exceção será lançada.
        /// </remarks>
        public class AsDoubleProperty : AsRealProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsDoubleProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como double.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para double, uma exceção será lançada.
            /// </remarks>
            public double this[string asKey]
            {
                get { return (double)GetValue(asKey); }
                set { this.ioOwner[asKey] = value; }
            }
        } // AsDoubleProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// DateTime.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para DateTime, uma exceção será lançada.
        /// </remarks>
        public class AsDateTimeProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsDateTimeProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como DateTime.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para DateTime, uma exceção será lançada.
            /// </remarks>
            public DateTime this[string asKey]
            {
                get
                {
                    object loValue = this.ioOwner[asKey];
                    if (loValue == null)
                        throw new NullReferenceException("Property value is null.");
                    return Convert.ToDateTime(loValue);
                }
                set { this.ioOwner[asKey] = value; }
            }

        } // AsDateTimeProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// PropertyList.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para PropertyList, uma exceção será lançada.
        /// </remarks>
        public class AsPropertyListProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsPropertyListProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como PropertyList.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para PropertyList, uma exceção será lançada.
            /// </remarks>
            public PropertyList this[string asKey]
            {
                get
                {
                    return (PropertyList)this.ioOwner[asKey];
                }
                set { this.ioOwner[asKey] = value; }
            }

        } // AsPropertyListProperty

        /// <summary>
        /// Classe que retorna o valor armazenado em um objeto PropertyList como
        /// decimal.
        /// </summary>
        /// <remarks>
        /// Caso o valor não possa ser convertido para decimal, uma exceção será lançada.
        /// </remarks>
        public class AsDecimalProperty : AsProperty
        {
            /// <summary>
            /// Construtor.
            /// </summary>
            /// <param name="aoOwner">Objeto IDictionary.</param>
            public AsDecimalProperty(IDictionary aoOwner) : base(aoOwner) { }

            /// <summary>
            /// Retorna o valor da propriedade como decimal.
            /// </summary>      
            /// <remarks>
            /// Caso o valor não possa ser convertido para decimal, uma exceção será lançada.
            /// </remarks>
            public decimal this[string asKey]
            {
                get
                {
                    object loValue = this.ioOwner[asKey];
                    if (loValue == null)
                        return 0;
                    else
                        return Convert.ToDecimal(loValue);
                }
                set { this.ioOwner[asKey] = value; }
            }

        } // AsDecimalProperty

        #endregion

    } // PropertyList


    public enum PropertyListChangeReason { EndUpdate, ItemAdded, ItemRemoved, Clear };

    public class PropertyListChangedEventArgs : EventArgs
    {

        public readonly PropertyListChangeReason ChangeReason;
        public readonly string Key;

        public PropertyListChangedEventArgs(PropertyListChangeReason aoChangeReason,
          string asKey)
        {
            this.ChangeReason = aoChangeReason;
            this.Key = asKey;
        }

    }

    /// <summary>
    /// Tipos possíveis de serialização em XML para um objeto PropertyList.
    /// </summary>
    public enum XmlStyle
    {
        /// <summary>
        /// Os pares são armazenados em uma string.
        /// </summary>
        AsString,

        /// <summary>
        /// As propriedades são armazenadas no formato de lista, estando no atributo "key" 
        /// o nome da propriedade. Este formato deve ser usado quando os nomes das propriedades
        /// não servirem como identificadores das tags do xml.
        /// </summary>
        AsList,

        /// <summary>
        /// Serialização padrão.
        /// </summary>
        Normal
    };

} // Trends.TFC.Common
